﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using TreeSharp;


namespace DiPlaner
{/*
    public class DiRunTime
    {
        #region VARIABLES

        public static List<SlotData> RuningSlots = new List<SlotData>();
        
        #endregion

        #region Structs

        public struct SlotData
        {
            public DateTime timeStamp;
            public int pId;
        }

        [Flags]
        public enum MouseEventFlags : uint
        {
            LEFTDOWN = 0x00000002,
            LEFTUP = 0x00000004,
            MIDDLEDOWN = 0x00000020,
            MIDDLEUP = 0x00000040,
            MOVE = 0x00000001,
            ABSOLUTE = 0x00008000,
            RIGHTDOWN = 0x00000008,
            RIGHTUP = 0x00000010,
            WHEEL = 0x00000800,
            XDOWN = 0x00000080,
            XUP = 0x00000100
        }

        [StructLayout(LayoutKind.Sequential)]
        struct WINDOWINFO
        {
            public uint cbSize;
            public RECT rcWindow;
            public RECT rcClient;
            public uint dwStyle;
            public uint dwExStyle;
            public uint dwWindowStatus;
            public uint cxWindowBorders;
            public uint cyWindowBorders;
            public ushort atomWindowType;
            public ushort wCreatorVersion;

            public WINDOWINFO(Boolean? filler)
                : this()   // Allows automatic initialization of "cbSize" with "new WINDOWINFO(null/true/false)".
            {
                cbSize = (UInt32)(Marshal.SizeOf(typeof(WINDOWINFO)));
            }

        }

        [StructLayout(LayoutKind.Sequential)]
        struct RECT
        {
            public int left, top, right, bottom;
        }
        
        #endregion

        #region Imports
        
        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);

        [DllImport("user32.dll", EntryPoint = "SetWindowText", CharSet = CharSet.Ansi)]
        public static extern bool SetWindowText(IntPtr hWnd, String strNewWindowName);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern bool SetCursorPos(int X, int Y);

        [DllImport("user32.dll")]
        static extern void mouse_event(int dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        
        #endregion

        public static bool RunSlot(int slot)
        {
            if (!CurrentProfile.Bots[slot].ready && CurrentProfile.WorkPlan.Schedule[slot].status != Profile.BotStatus.running)
            {
                DiForm.Worker.LogTask("Bot #" + (slot + 1) + " not ready!");
                return false;
            }
            else if (!RunBot(CurrentProfile.Bots[slot], slot))
            {
                DiForm.Worker.LogTask("Failed to run Bot #" + (slot + 1));
                return false;
            }
            else return true;
        }
        
        public static bool RunBot(Profile.BotProfile BOT, int slot)
        {
            try
            {
                #region Start D3
                DiForm.Worker.AddTask(new Task(() =>
                {

                    TimeSpan stime = (CurrentProfile.WorkPlan.workTime - (DateTime.Now - CurrentProfile.WorkPlan.Schedule[slot].startTime)) + new TimeSpan(0, 0, 1);
                    DiForm.Worker.LogTask("++Starting Bot #" + (slot + 1) + " for " + string.Format("{0:00} hour(s)", (int)stime.TotalHours / 1));
                    
                    Process proc = new Process();

                    proc.StartInfo = new ProcessStartInfo(BOT.d3_path, "-launch");
                    proc.StartInfo.UseShellExecute = true;
                    proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(BOT.d3_path);

                    try
                    {
                        proc.Start();
                        Thread.Sleep(1000);

                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " D3 waiting to idle...");
                        if (!proc.WaitForInputIdle(60000) || !proc.Responding) throw new Exception("Waiting time left.");
                        Thread.Sleep(10000);
                        RuningSlots[slot].D3_PID = proc.Id;
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " D3 Started");
                    }
                    catch (Exception e)
                    {
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " D3 run error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
                    }

                    Program._moveWindow(slot + 1);
                }));
                #endregion

                #region Start DB
                DiForm.Worker.AddTask(new Task(() =>
                {
                    string bname = "runer_" + (slot + 1);
                    try { bname = Regex.Match(BOT.bnet, "^[A-Z0-9._%+-]*", RegexOptions.IgnoreCase).Value; } catch { }

                    string param = string.Format(
                        " -key=\"{0}\" -pid=\"{1}\" -bnetaccount=\"{2}\" -bnetpassword=\"{3}\" -windowTitle=\"{4}\" -routine=\"{5}\" -minutes=\"{6}\" -noflash -autostart",
                        BOT.db_key, 
                        RuningSlots[slot].D3_PID, 
                        BOT.bnet, 
                        BOT.pass, 
                        bname, 
                        BOT.db_routine, 
                        (int)(CurrentProfile.WorkPlan.workTime.TotalMinutes + 10)
                        );
                    try
                    {
                        Process proc = new Process();
                        proc.StartInfo = new ProcessStartInfo(BOT.db_path, param);
                        proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(BOT.db_path);
                        proc.Start();
                        Thread.Sleep(1000);
                        if (!proc.WaitForInputIdle(60000) || !proc.Responding) throw new Exception("Waiting time left.");


                        RuningSlots[slot].DB_PID = proc.Id;
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " DB Started");
                        proc.Dispose();
                        proc = null;

                        Thread.Sleep(1000);
                        if (SetWindowText(Process.GetProcessById(RuningSlots[slot].D3_PID).MainWindowHandle, bname)) Thread.Sleep(1000);
                    }
                    catch (Exception e)
                    {
                        DiForm.Worker.LogTask("Bot #" + slot + " DB run error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
                    }

                    DiForm.Worker.LogTask("Bot #" + (slot + 1) + " on route");
                }));
                #endregion
            }
            catch (Exception) { return false; }
            return true;
        }


        public static void StopSlot(int slot)
        {
            try
            {
                Profile.BotProfile BOT = CurrentProfile.Bots[slot];

                DiForm.Worker.AddTask(new Task(() =>
                    {
                        Process proc = new Process();
                        try
                        {
                            #region Kill D3
                            DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Atempt to stop...");

                            proc = FindProcess(BOT.d3_path);
                            if (proc != null && proc.Id != 0)
                            {
                                proc.Kill();
                                proc.Dispose();
                                Thread.Sleep(422);
                            }
                            #endregion
                            #region Kill DB

                            proc = FindProcess(BOT.db_path);
                            if (proc != null && proc.Id != 0)
                            {
                                proc.Kill();
                                proc.Dispose();
                                Thread.Sleep(329);
                            }
                            #endregion
                        }
                        catch { }
                    }));
            }
            catch (Exception e)
            {
                DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Stop attempt return an error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
            }
        }

        public static void StopAllSlots()
        {
            DiForm.Worker.ClearQueue();
            for (int i = 0; i < 8; i++) StopSlot(i);
        }

        public static bool ClickStartStop(int slot)
        {
            SlotData curSlot = RuningSlots[slot];
            Profile.BotProfile BOT = CurrentProfile.Bots[slot];

            try
            {
                DiForm.Worker.AddTask(new Task(() =>
                {
                    try
                    {
                        #region Stop/Start DB
                        Process proc = FindProcess(BOT.db_path);
                        if (proc != null || proc.Id <= 0)
                        {
                            SendLeftClick(proc.MainWindowHandle, 180, 47);

                            Thread.Sleep(329);
                            DiForm.Worker.LogTask("Bot #" + (slot + 1) + " DB Click succes");
                        }
                        else throw new Exception("DB process not found");
                        #endregion
                    }
                    catch (Exception e)
                    {
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Stop/Start click attempt return an error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
                    }
                }));
            }
            catch (Exception e)
            {
                DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Stop/Start click attempt return an error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
                return false;
            }

            return true;
        }

        public static bool CheckNetState(int pid = 0, string path = "")
        {
            if (pid == 0 || path.Length <= 0) return false;
            bool ret = false;

            foreach (TcpRow tcpRow in ManagedIpHelper.GetExtendedTcpTable(true))
            {
                try
                {
                    Process proc = Process.GetProcessById(tcpRow.ProcessId);
                    if (proc.ProcessName == "System") continue;
                    if (pid == tcpRow.ProcessId && tcpRow.RemoteEndPoint.Port == 1119 && tcpRow.State == System.Net.NetworkInformation.TcpState.Established) ret = true;
                    proc.Dispose();
                }
                catch { }
            }
            return ret;
        }

        public static void CheckSlot(int slot)
        {
            try
            {
                DiForm.Worker.AddTask(new Task(() =>
                {
                    Profile.BotStart SS = CurrentProfile.WorkPlan.Schedule[slot];
                    if (SS.status != Profile.BotStatus.running) return;

                    DiForm.Worker.LogTask("..Checking Bot #" + (slot + 1));

                    Profile.BotProfile BOT = CurrentProfile.Bots[slot];
                    SlotData curSlot = RuningSlots[slot];

                    try
                    {
                        #region Check D3
                        Process prod = FindProcess(BOT.d3_path);
                        if (prod == null || prod.Id <= 0 || !CheckNetState(prod.Id, BOT.d3_path) || !prod.Responding)
                        {
                            DiForm.Worker.LogTask("Bot #" + (slot + 1) + " D3 not responding or crashed");
                            if (prod != null && prod.Id > 0)
                            {
                                DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Kill D3");
                                prod.Kill();
                                prod.Dispose();
                                DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Wait DB for auto quit");
                                Thread.Sleep(5000);
                                prod = FindProcess(BOT.db_path);
                                if (prod != null && prod.Id > 0)
                                {
                                    DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Force kill DB");
                                    prod.Kill();
                                    prod.Dispose();
                                    DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Wait for possible session drop...");
                                    Thread.Sleep(15000);
                                }
                            }
                            SS.status = Profile.BotStatus.crashed;
                            CurrentProfile.WorkPlan.Schedule[slot] = SS;
                        }
                        #endregion
                    }
                    catch (Exception e)
                    {
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Checking slot status return an error: " + string.Format("\r\n {0}\r\n {1}\r\n {2}", e.TargetSite.Name, e.Source, e.Message));
                        SS.status = Profile.BotStatus.crashed;
                        CurrentProfile.WorkPlan.Schedule[slot] = SS;
                        DiForm.Worker.LogTask("Bot #" + (slot + 1) + " Wait for possible session drop...");
                        Thread.Sleep(15000);
                    }
                }));
            }
            catch { }
        }

        #region Save/Load RS
        /*
        public static void SaveRuningSlots()
        {
            try
            {
                string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "rs.db";

                var serializer = new XmlSerializer(typeof(SlotData[]));
                if (path.Length > 0) using (TextWriter writer = new StreamWriter(path))
                    {
                        serializer.Serialize(writer, RuningSlots);
                    }
            }
            catch { }
        }


        public static void LoadRuningSlots()
        {
            SlotData[] rs = new SlotData[8];
            try
            {
                string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "rs.db";

                var serializer = new XmlSerializer(typeof(SlotData[]));
                if (File.Exists(path)) using (TextReader reader = new StreamReader(path))
                        rs = (SlotData[])serializer.Deserialize(reader);
            }
            catch { }
            RuningSlots = rs;
        }//*//*
        #endregion

        #region Process Utils
        
        public static Process FindProcess(string path, string name = "")
        {
            Process proc = new Process();
            if (name.Length > 0)
            {
                foreach (Process _proc in Process.GetProcessesByName(name))
                {
                    try
                    {
                        if (_proc.MainModule.FileName == path) proc = _proc;
                    }
                    catch { }

                }
            }
            if (name == "" || proc == null || proc.Id <= 0)
            {
                foreach (Process _proc in Process.GetProcesses())
                {
                    try
                    {
                        if (_proc.MainModule.FileName == path) return _proc;
                    }
                    catch { }

                }
            }
            return null;
        }
        
        #endregion

        #region Mouse

        public static void SendLeftClick(IntPtr hWnd, int x, int y)
        {
            WINDOWINFO wi = new WINDOWINFO(false);
            GetWindowInfo(hWnd, ref wi);

            int old_x, old_y;
            old_x = Cursor.Position.X;
            old_y = Cursor.Position.Y;

            SetCursorPos(x, y);

            SetForegroundWindow(hWnd);

            mouse_event((int)MouseEventFlags.LEFTDOWN, wi.rcClient.left + x, wi.rcClient.top + y, 0, UIntPtr.Zero);
            mouse_event((int)MouseEventFlags.LEFTUP, wi.rcClient.left + x, wi.rcClient.top + y, 0, UIntPtr.Zero);

            SetCursorPos(old_x, old_y);
        }

        #endregion

    }


    public class WorkerClass
    {
        public BlockingCollection<Task> BCQ = new BlockingCollection<Task>(new ConcurrentQueue<Task>());
        public BlockingCollection<string> BCQLog = new BlockingCollection<string>(new ConcurrentQueue<string>());
        public static CancellationTokenSource TokenSource = new CancellationTokenSource();
        public CancellationToken Token = TokenSource.Token;

        public delegate List<string> LogParser(string text, bool flag);

        public void RunWorker(TextBox tb, LogParser logParser)
        {
            #region Work task
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Task work = BCQ.Take();
                    try
                    {
                        DiForm.CheckAllow = false;
                        work.Start();
                        work.Wait();
                    }
                    catch (AggregateException exArg)
                    {
                        foreach (Exception ex in exArg.InnerExceptions)
                        {
                            if (tb.InvokeRequired)
                            {
                                tb.Invoke((MethodInvoker)delegate
                                {
                                    foreach (string ss in logParser(ex.Message, true))
                                    {
                                        tb.AppendText(ss);
                                    }
                                });
                            }
                        }
                        DiForm.CheckAllow = true;
                    }
                    finally
                    {
                        work.Dispose();
                        DiForm.CheckAllow = true;
                    }
                }
            }, Token);
            #endregion

            #region Log Task
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    string msg = BCQLog.Take();
                    try
                    {
                        Task.Factory.StartNew(() =>
                            {
                                if (tb.InvokeRequired)
                                {
                                    tb.Invoke((MethodInvoker)delegate
                                    {
                                        foreach (string ss in logParser(msg, true))
                                        {
                                            tb.AppendText(ss);
                                        }
                                    });
                                }
                            });
                    }
                    catch (AggregateException exArg)
                    {
                        foreach (Exception ex in exArg.InnerExceptions)
                        {
                            if (tb.InvokeRequired)
                            {
                                tb.Invoke((MethodInvoker)delegate
                                {
                                    foreach (string ss in logParser(ex.Message, true))
                                    {
                                        tb.AppendText(ss);
                                    }
                                });
                            }
                        }
                    }
                }
            }, Token);
            #endregion
        }

        public void AddTask(Task newTask)
        {
            BCQ.Add(newTask);
        }

        public void LogTask(string msg)
        {
            BCQLog.Add(msg);
        }

        public void ClearQueue()
        {
            BCQ.Dispose();
            BCQ = new BlockingCollection<Task>(new ConcurrentQueue<Task>());
        }

        public void StopWork()
        {
            ClearQueue();
            TokenSource.Cancel();
        }
    }//*/
}
